package udtp

import (
    "net"
    "time"
    "errors"
    "sync/atomic"
)

type serverClient struct {
    udtp *UDTP
    conn *net.UDPConn
    remote *net.UDPAddr
    seq uint32
    ack uint32
    status uint8
    data []byte
    bufIn chan []byte
    end bool
    TOSecond int8
}

func NewServerClient(u *UDTP,conn *net.UDPConn, addr *net.UDPAddr) (*serverClient) {
    sc := new(serverClient)
    sc.udtp = u
    sc.conn = conn
    sc.remote = addr
    sc.bufIn = make(chan []byte, 8)
    sc.end = false
    sc.status = 0xff
    sc.TOSecond = 10
    go sc.mainLoop()
    return sc
}

func (sc *serverClient) CloseForced() {
    if !sc.end {
        sc.end = true
        sc.udtp.deleteClient(sc.remote)
    }
}

func (sc *serverClient) mainLoop() {
    for {
        if sc.end { //ServerClient is closed.
            break
        }

        select {
            case sc.data = <-sc.bufIn:
                //DebugPrint("get data")
            case <-time.After(time.Duration(sc.TOSecond) * time.Second):
                DebugPrint("timeout", sc.remote.IP.String(), sc.remote.Port)
                sc.udtp.deleteClient(sc.remote)
                sc.end = true
                return
        }
        if sc.data[0] == 0x02 {
            DebugPrint("get syn request")
            sc.handleSynRequest()
            continue
        }
        if sc.data[0] == 0x01 {
            DebugPrint("get ack")
            sc.handleACK()
            continue
        }
    }
}

func (sc *serverClient) handleSynRequest() {
    if sc.status == 0xff || sc.status == 0x00 {
        sc.SetScSEQ(uint16(time.Now().UnixNano()))
        sc.SetScACK(getSEQ(sc.data) + 1)
        sc.SendWithType(3, nil) //SYN ACK type:0x03
        DebugPrint("syn ack sent SEQ:", sc.GetScSEQ())
        sc.status = 0x00
        return
    }
}

func (sc *serverClient) handleACK() {
    if sc.status == 0x00 {
        pktACK := getACK(sc.data)
        DebugPrint("pktack:", pktACK)
        if pktACK == sc.GetScSEQ() + 1{
            sc.TOSecond = 60
            sc.status = 0x01
            DebugPrint("connection established", sc.remote.IP.String(), sc.remote.Port)
            //Connection is established.
            //Timeout second is 60.
        }
    }
}

func (sc *serverClient) SendWithType(msgType byte, data []byte) (err error) {
    err = nil
    if len(data) > 1024 {
        err = errors.New("data is too long")
        return
    }
    pkt := makePacket(msgType, sc.GetScSEQ(), sc.GetScACK(), data)
    sc.udtp.conn.WriteToUDP(pkt, sc.remote)
    return
}

func (sc *serverClient) GetScSEQ() uint16 {
    return uint16(atomic.LoadUint32(&sc.seq))
}

func (sc *serverClient) GetScACK() uint16 {
    return uint16(atomic.LoadUint32(&sc.ack))
}

func (sc *serverClient) SetScSEQ(val uint16) {
    atomic.StoreUint32(&sc.seq, uint32(val))
}

func (sc *serverClient) SetScACK(val uint16) {
    atomic.StoreUint32(&sc.ack, uint32(val))
}

func makePacket(pktType byte, seq uint16, ack uint16, data []byte) (ret []byte) {
    ret = append(ret, pktType, 0, 0, 0)
    ret = append(ret, byte((seq & 0xFF00) >> 8), byte(seq & 0xFF))
    ret = append(ret, byte((ack & 0xFF00) >> 8), byte(ack & 0xFF))
    ret = append(ret, data...)
    return
}

func getSEQ (pkt []byte) (uint16) {
    return (uint16(pkt[4]) << 8) + uint16(pkt[5])
}

func getACK (pkt []byte) (uint16) {
    return (uint16(pkt[6]) << 8) + uint16(pkt[7])
}